package main

import (
	"encoding/json"
	"errors"
	"github.com/garyburd/redigo/redis"
	"github.com/ngaut/log"
	"github.com/shopspring/decimal"
	"global/function"
	"global/structs"
	"math"
	"server/ote/model"
	"strconv"
	"strings"
	"time"
)

const DEFAULT_WAIT_COUNTS = 10

type trustItem struct {
	Id    int
	Price decimal.Decimal
}

type TrustItemSlice []trustItem

func (t TrustItemSlice) Len() int {
	return len(t)
}

func (t TrustItemSlice) Swap(i, j int) {
	t[i], t[j] = t[j], t[i]
}

func (t TrustItemSlice) Less(i, j int) bool {
	if t[i].Price.Cmp(t[j].Price) == 0 {
		return t[j].Id > t[i].Id
	}
	return t[i].Price.Cmp(t[j].Price) == 1
}

// 交易日志
func NewOteLog(results *structs.Results, taskType structs.TaskType) *structs.OteLog {
	content, _ := resultToString(results)
	var isRedisSuccess, isDbSuccess int
	if results.RedisOptSuccess {
		isRedisSuccess = 1
	}
	if results.DbOptSuccess {
		isDbSuccess = 1
	}
	return &structs.OteLog{
		ActiveTrustId:   results.ActiveTrust.TrustId,
		ActiveUid:       results.ActiveTrust.Uid,
		IsRedisSuccess:  isRedisSuccess,
		IsDbSuccess:     isDbSuccess,
		ActiveTrustType: string(taskType),
		ResultContent:   content,
		CreateTime:      int(time.Now().Unix()),
		UpdateTime:      time.Now(),
	}
}

func NewOteLogWithoutResults(trustCoin structs.TrustCoin, redisSuccess, dbSuccess bool) *structs.OteLog {

	var isRedisSuccess, isDbSuccess int
	if redisSuccess {
		isRedisSuccess = 1
	}

	if dbSuccess {
		isDbSuccess = 1
	}

	trustStr, err := json.Marshal(trustCoin)
	if err != nil {
		return nil
	}

	return &structs.OteLog{
		ActiveTrustType: 		string(structs.TASK_CANCEL),
		ActiveTrustId: 			trustCoin.Id,
		ActiveUid: 				trustCoin.Uid,
		IsRedisSuccess: 		isRedisSuccess,
		IsDbSuccess: 			isDbSuccess,
		ResultContent: 			string(trustStr),
		Checked: 				0,
		CreateTime: 			int(time.Now().Unix()),
		UpdateTime: 			time.Now(),
	}
}

// 将结果转成JSON字符串
func resultToString(results *structs.Results) (string, error) {
	resultBytes, err := json.Marshal(results)
	return string(resultBytes), err
}

// 重塑Redis数据
func rebuildRedisDataBak(conn redis.Conn, results *structs.Results) (bool, error) {

	if !results.IsDealed {
		return false, errors.New("trust not deal")
	}

	if results.DbOptSuccess {
		return false, errors.New("operate db success, no need rebuild redis")
	}

	if !results.RedisOptSuccess {
		return false, errors.New("operate redis failed, no need rebuild redis")
	}

	coinPair := function.CoinPairName(results.CoinFrom, results.CoinTo)

	conn.Send("MULTI")

	// 主动单添加
	if _, err := updateTrustAndUserDetailAndWithMulti(
		conn,
		coinPair,
		structs.TaskType(results.ActiveTrustType),
		results.ActiveTrust.TrustId,
		results.ActiveTrust.Uid,
		results.ActiveTrust.OriginalTrustCoin); err != nil {
			log.Errorf("rebuild trust detail failed")
			conn.Do("DISCARD")
			return false, err
	}
	log.Debugf("add trust to user and detail, tid: %d, uid: %d",
		results.ActiveTrust.TrustId, results.ActiveTrust.Uid)

	// 只有当主动单全部交易后才会需要加入等待集合
	if results.ActiveTrust.TrustDealStatus == structs.TRUST_STATUS_ALL {
		_, err := addTrustToWaitWithMulti(
			conn,
			structs.TaskType(results.ActiveTrustType),
			coinPair,
			results.ActiveTrust.TrustId,
			results.ActiveTrust.OriginalTrustCoin.Price)
		if err != nil {
			log.Errorf("add trust to wait list failed, tid: %d", results.ActiveTrust.TrustId)
			conn.Do("DISCARD")
			return false, err
		}
		log.Debugf("add trust to wait success, tid: %d, type: %s",
			results.ActiveTrust.TrustId, results.ActiveTrust.OriginalTrustCoin.Flag)
	}

	for _, passive := range results.PassiveTrusts {
		// 被动单添加到Detail表
		if _, err := updateTrustAndUserDetailAndWithMulti(conn,
			coinPair,
			structs.TaskType(passive.OriginalTrustCoin.Flag),
			passive.TrustId,
			passive.Uid,
			passive.OriginalTrustCoin); err != nil {
				log.Errorf("rebuild trust detail failed")
				conn.Do("DISCARD")
				return false, err
		}
		log.Debugf("update trust user and detail success, tid: %d", passive.TrustId)

		// 只有当主动单全部交易后才会需要加入等待集合
		if passive.TrustDealStatus == structs.TRUST_STATUS_ALL {
			_, err := addTrustToWaitWithMulti(conn,
				structs.TaskType(passive.OriginalTrustCoin.Flag),
				coinPair,
				passive.TrustId,
				passive.OriginalTrustCoin.Price)
			if err != nil {
				log.Errorf("add trust to wait list failed, tid: %d", passive.TrustId)
				conn.Do("DISCARD")
				return false, err
			}
			log.Debugf("add pass trust to wait success, tid: %d", passive.TrustId)
		}
	}

	conn.Do("EXEC")
	log.Debugf("rebuild trust completed")
	return true, nil
}

// 将队列数据转成TrustCoin结构
func transQueueDataToTrustCoin(queue structs.Queue, pair structs.CoinPair) (*structs.TrustCoin, error) {

	d := queue.Data

	var v = strings.TrimSpace(strings.Trim(string(d.UpdateTime), "\""))
	t, err := time.ParseInLocation("2006-01-02 15:04:05", v, time.Local)
	if err != nil {
		return nil, err
	}

	return &structs.TrustCoin{
		Id:              d.Id,
		Uid:             d.Uid,
		Flag:            d.Flag,
		CoinFrom:        d.CoinFrom,
		CoinTo:          d.CoinTo,
		Number:          d.Number,
		NumberAvailable: d.NumberAvailable,
		NumberDeal:      d.NumberDeal,
		Price:           d.Price,
		IsNew:           "0",				// 已被处理过了
		Status:          d.Status,
		CreateTime:      d.CreateTime,
		UpdateTime:      t,
	}, nil
}

func saveOrdersToRedis(conn redis.Conn, activeId int, coinPair string, trustType structs.TaskType, logs []*structs.OrderLogs) (bool, error) {
	orderKey := function.GetOrderKey(coinPair)
	orderServerKey := function.GetOteOrderList(coinPair)
	for _, l := range logs {
		nowTime := time.Now()
		orderData := structs.Order{
			Id: 		l.Order.Id,
			ActiveId: 	activeId,
			CreateTime: nowTime.Format("15:04:05"),
			Price:      l.Order.Price,
			Number:     l.Order.Number,
			Flag:       string(trustType),
			Timestamp:  nowTime.Unix(),
			Total: 		l.DealTotal.StringFixedBank(16),
		}

		data, err := json.Marshal(orderData)
		if err != nil {
			return false, err
		}

		// 所有成交ORDER
		err = conn.Send("LPUSH", orderKey, string(data))
		if err != nil {
			return false, err
		}

		// Order 服务器分析
		err = conn.Send("RPUSH", orderServerKey, string(data))
		if err != nil {
			return false, err
		}

		var buyerData, sellerData []byte
		if trustType == structs.TASK_BUY {
			buyerData = data
			orderData.Flag = string(structs.TASK_SALE)
			sellerData, _ = json.Marshal(orderData)
		} else {
			orderData.Flag = string(structs.TASK_BUY)
			buyerData, _ = json.Marshal(orderData)
			sellerData = data
		}

		// 保存到买家个人交易记录
		buyerKey := function.GetUserOrdersKey(coinPair, l.Order.BuyUid)
		conn.Send("LPUSH", buyerKey, string(buyerData))
		// 保留部分
		conn.Send("LTRIM", buyerKey, 0, 199)

		sellerKey := function.GetUserOrdersKey(coinPair, l.Order.SaleUid)
		conn.Send("LPUSH", sellerKey, string(sellerData))
		// 保留部分
		conn.Send("LTRIM", sellerKey, 0, 199)
	}

	return true, nil
}

// 保存数据到历史委托中
func saveTrustToHistoryDone(conn redis.Conn, coinPair string, uid int, coin *structs.TrustCoin) {
	key := function.GetUserHistoryTrustKey(coinPair, uid)
	historyData, _ := json.Marshal(coin)
	conn.Send("LPUSH", key, string(historyData))
	conn.Send("LTRIM", key, 0, 49)

	keyWithoutCoinPair := function.GetUserHistoryTrustWithoutCoinPairKey(uid)
	conn.Send("LPUSH", keyWithoutCoinPair, string(historyData))
	conn.Send("LTRIM", keyWithoutCoinPair, 0, 49)
}

func copyTrustCoin(oldTrustCoin *structs.TrustCoin, available,
	deal string, status structs.TrustStatus) *structs.TrustCoin {
	var trustCoin = new(structs.TrustCoin)
	*trustCoin = *oldTrustCoin
	trustCoin.NumberAvailable = available
	trustCoin.NumberDeal = deal
	trustCoin.Status = int(status)
	trustCoin.IsNew = "0"
	trustCoin.UpdateTime = time.Now()
	return trustCoin
}

// 复制TrustCoin结构
func copyWithStruct(coin structs.TrustCoin) *structs.TrustCoin {
	var trustCoin = new(structs.TrustCoin)
	*trustCoin = coin
	return trustCoin
}

// 通过指针复制
func copyWithPoint(coin *structs.TrustCoin) *structs.TrustCoin {
	var trustCoin = new(structs.TrustCoin)
	*trustCoin = *coin
	return trustCoin
}

func updateTrustAndUserDetailAndWithMulti(conn redis.Conn, coinPair string,
	coinType structs.TaskType, tid, uid int, data *structs.TrustCoin) (bool, error) {

	marshalStr, err := json.Marshal(data)
	if err != nil {
		return false, err
	}

	var trustKey string
	if coinType == structs.TASK_BUY {
		trustKey = function.GetBuyTrustDetailKey(coinPair)
	} else {
		trustKey = function.GetSaleTrustDetailKey(coinPair)
	}

	err = conn.Send("HSET", trustKey, tid, string(marshalStr))
	if err != nil {
		return false, err
	}

	userKey := function.GetUserTrustKey(string(coinType), coinPair, uid)
	err = conn.Send("HSET", userKey, tid, string(marshalStr))
	if err != nil {
		return false, err
	}

	return true, nil
}

// 删除委托单详情
func removeTrustDetailWithMulti(conn redis.Conn, coinPair string, coinType structs.TaskType, tid int, uid int) (bool, error) {
	var key string
	if coinType == structs.TASK_BUY {
		key = function.GetBuyTrustDetailKey(coinPair)
	} else {
		key = function.GetSaleTrustDetailKey(coinPair)
	}

	err := conn.Send("HDEL", key, tid)
	if err != nil {
		return false, err
	}

	err = conn.Send("HDEL", function.GetUserTrustKey(string(coinType), coinPair, uid), tid)
	if err != nil {
		return false, err
	}
	return true, nil
}

// 从待匹配列表删除元素
func removeTrustFromWaitWithMulti(conn redis.Conn, coinType structs.TaskType, coinPair string, tid int) (bool, error) {
	var key string
	if coinType == structs.TASK_SALE {
		tid = tidToSaleWaitListAdd(tid)
		key = function.GetWaitSaleKey(coinPair)
	} else {
		tid = tidToBuyWaitListAdd(tid)
		key = function.GetWaitBuyKey(coinPair)
	}

	err := conn.Send("ZREM", key, tid)
	if err != nil {
		return false, err
	}
	return true, nil
}

// 从待匹配列表删除元素
func removeTrustFromWaitWithoutMulti(conn redis.Conn, coinType structs.TaskType, coinPair string, tid int) (bool, error) {
	var key string
	if coinType == structs.TASK_SALE {
		tid = tidToSaleWaitListAdd(tid)
		key = function.GetWaitSaleKey(coinPair)
	} else {
		tid = tidToBuyWaitListAdd(tid)
		key = function.GetWaitBuyKey(coinPair)
	}

	err := conn.Send("ZREM", key, tid)
	if err != nil {
		return false, err
	}
	return true, nil
}

// 非事务执行添加委托单到集合
func addTrustToWaitWithMulti(conn redis.Conn, trustType structs.TaskType,
	coinPair string, tid int, price string) (bool, error) {
	var key string
	if trustType == structs.TASK_BUY {
		tid = tidToBuyWaitListAdd(tid)
		key = function.GetWaitBuyKey(coinPair)
	} else {
		tid = tidToSaleWaitListAdd(tid)
		key = function.GetWaitSaleKey(coinPair)
	}
	err := conn.Send("ZADD", key, price, tid)
	if err != nil {
		return false, err
	}
	return true, nil
}

// 添加到卖家待匹配的列表时需要加上基数
// 返回加上基数后的委托单ID
func tidToSaleWaitListAdd(tid int) int {
	return int(math.Pow10(15)) + tid
}

// 从买家列表里面获取的数据需要减去这个基数
func tidToSaleWaitListSub(tid int) int {
	return tid - int(math.Pow10(15))
}

func tidToBuyWaitListAdd(tid int) int {
	return int(math.Pow10(16)) - tid
}

func tidToBuyWaitListSub(tid int) int {
	return int(math.Pow10(16)) - tid
}

// 非事务执行添加委托单到集合
func addTrustToWaitWithoutMulti(conn redis.Conn, trustType structs.TaskType, coinPair string, tid int, price string) (bool, error) {
	var key string
	if trustType == structs.TASK_BUY {
		tid = tidToBuyWaitListAdd(tid)
		key = function.GetWaitBuyKey(coinPair)
	} else {
		tid = tidToSaleWaitListAdd(tid)
		key = function.GetWaitSaleKey(coinPair)
	}

	_, err := conn.Do("ZADD", key, price, tid)
	if err != nil {
		return false, err
	}
	return true, nil
}

// 格式化数据
func formatDecimal2String(num decimal.Decimal, decil int) string {
	return num.StringFixedBank(int32(decil))
}

func createOrderFinance(fromUid, toUid int, coinFrom, coinTo, txtCoin string, txtNumber string, flag int) *structs.OrderCoinFinance {
	return &structs.OrderCoinFinance{
		UidFrom:    fromUid,
		UidTo:      toUid, // 支付对象
		CoinFrom:   coinFrom,
		CoinTo:     coinTo,
		TaxCoin:    txtCoin,
		TaxNumber:  txtNumber,
		Flag:       flag,
		CreateTime: time.Now(),
		UpdateTime: time.Now(),
	}
}

func getMatchedBuyTrustsFromRedis(conn redis.Conn, price, coinPair string) ([]trustItem, error) {
	key := function.GetWaitBuyKey(coinPair)
	trusts, err := redis.StringMap(conn.Do("ZREVRANGEBYSCORE", key, "+inf", price, "WITHSCORES", "LIMIT", 0, DEFAULT_WAIT_COUNTS))
	if err != nil {
		return nil, err
	}
	var trustItems []trustItem
	for item := range trusts {
		id, _ := strconv.Atoi(item)
		id = tidToBuyWaitListSub(id)
		pObj, _ := decimal.NewFromString(trusts[item])
		trustItems = append(trustItems, trustItem{
			Id:    id,
			Price: pObj,
		})
	}
	return trustItems, nil
}

func getMatchedSaleTrustsFromRedis(conn redis.Conn, price, coinPair string) ([]trustItem, error) {
	key := function.GetWaitSaleKey(coinPair)
	trusts, err := redis.StringMap(conn.Do("ZRANGEBYSCORE", key, 0, price, "WITHSCORES", "LIMIT", 0, DEFAULT_WAIT_COUNTS))
	if err != nil {
		return nil, err
	}
	var trustItems []trustItem
	for item := range trusts {
		id, _ := strconv.Atoi(item)
		id = tidToSaleWaitListSub(id)
		pObj, _ := decimal.NewFromString(trusts[item])
		trustItems = append(trustItems, trustItem{
			Id:    id,
			Price: pObj,
		})
	}
	return trustItems, nil
}

// 从 Redis 获取详情
func getTrustDetailFromRedis(conn redis.Conn, trustType structs.TaskType, coinPair string, tid int) (*structs.TrustCoin, error) {
	var key string
	if trustType == structs.TASK_BUY {
		key = function.GetBuyTrustDetailKey(coinPair)
	} else {
		key = function.GetSaleTrustDetailKey(coinPair)
	}

	d, err := redis.String(conn.Do("HGET", key, tid))
	if err != nil {
		return model.TrustCoinModel.Get(tid)
	}

	var data structs.TrustCoin
	err = json.Unmarshal([]byte(d), &data)
	return &data, err
}

func checkIfInWaiting(conn redis.Conn, coinType structs.TaskType, coinPair string, tid int) bool {
	var key string
	if coinType == structs.TASK_BUY {
		tid = tidToBuyWaitListAdd(tid)
		key = function.GetWaitBuyKey(coinPair)
	} else {
		tid = tidToSaleWaitListAdd(tid)
		key = function.GetWaitSaleKey(coinPair)
	}

	_, err := redis.String(conn.Do("ZSCORE", key, tid))
	if err != nil {
		log.Errorf("no item: %s", err.Error())
		return false
	}
	return true
}

// 检查订单是否是Waiting状态
func checkIsWaitingAndRemoveIfExists(conn redis.Conn, coinType structs.TaskType, coinPair string, tid int) (bool, error) {
	var key string
	if coinType == structs.TASK_BUY {
		tid = tidToBuyWaitListAdd(tid)
		key = function.GetWaitBuyKey(coinPair)
	} else {
		tid = tidToSaleWaitListAdd(tid)
		key = function.GetWaitSaleKey(coinPair)
	}

	_, err := redis.String(conn.Do("ZSCORE", key, tid))
	if err != nil {
		log.Errorf("no item: %s", err.Error())
		return false, err
	}

	_, err = conn.Do("ZREM", key, tid)
	if err != nil {
		return false, err
	}

	return true, nil
}

func removeTrustDetailIfExists(conn redis.Conn, trustType structs.TaskType, coinPair string, tid, uid int) (bool, error) {

	// detail
	var detailKey, userKey string
	if trustType == structs.TASK_BUY {
		detailKey = function.GetBuyTrustDetailKey(coinPair)
	} else {
		detailKey = function.GetSaleTrustDetailKey(coinPair)
	}

	_, err := redis.Bool(conn.Do("HEXISTS", detailKey, tid))
	if err != nil {
		log.Errorf("check detail item exists error, msg: %s", err.Error())
	} else {
		_, err = conn.Do("HDEL", detailKey, tid)
		if err != nil {
			return false, err
		}
	}

	// user
	userKey = function.GetUserTrustKey(string(trustType), coinPair, uid)
	_, err = redis.Bool(conn.Do("HEXISTS", userKey, tid))
	if err != nil {
		return false, err
	} else {
		if _, err := conn.Do("HDEL", userKey, tid); err != nil {
			return false, err
		}
	}

	return true, nil
}

// 删除 order 记录多余数据
func trimOrderList(ctx *Context, coinPair string) {
	conn := ctx.GetOteRedis().Get()
	defer conn.Close()
	conn.Do("LTRIM", function.GetOrderKey(coinPair), 0, 499)
}